#include "util.h"
#include "notes_panel.h"
#include "dbase.h"
#include "settings.h"
#include "app_frame.h"

NotesPanel::NotesPanel(AppFrame *top_frame, wxWindow* parent, DBase *db, wxString *source)
    :NotesPanelBase(top_frame, parent)
{
    dbgenter("NotesPanel::NotesPanel()\n");

    this->source = source;
    this->db = db;
}

void NotesPanel::InsertNote(const Note &n)
{
    dbgenter("NotesPanelBase::InsertNote(%s)\n", n.ToString());

    // find where in vector it belongs, and insert it
    for (unsigned i=0; i < items.size(); i++) {
        if (items[i].isNote() && n.CompareDateTo(items[i].note()) <= 0) {
            InsertItemAt(TextItem(n), i);
            return;
        }
    }

    //new note must be last
    InsertItemAt(TextItem(n), items.size());
}

static bool IsSingleKeywordFilter(const NoteFilter& filter)
{
    return filter.keywords.size() == 1 && filter.from_date.IsEmpty() && filter.to_date.IsEmpty();
}

void NotesPanel::SetFilter(const NoteFilter& filter)
{
    dbgenter("NotesPanel::SetFilter(%s)\n", filter.ToString());
    this->filter = filter;
    render_options.highlight_body = filter.body;
    render_options.highlight_keywords = filter.keywords;

    // save old selection
    // TODO: improve this selection preservation to work for other items than just notes
    int old_selection_id = -1;
    if (selection_index != -1 && items[selection_index].isNote())
        old_selection_id = items[selection_index].note().id;

    items.clear();
    display_ok = false;

    // if filter is single keyword without dates, also look for keywords
    if (IsSingleKeywordFilter(filter)) {
        dbg("Looking up keywords\n");
        vector<Keyword> keywords;
        if (keyword_list.KeywordsByPartialName(keywords, filter.keywords[0])) {
            for (unsigned ki=0; ki<keywords.size(); ki++) {
                items.push_back(TextItem(keywords[ki]));
            }
        }
    }

    // load aliases for all keywords
    dbg("Looking up aliases\n");
    for (unsigned x=0; x<filter.keywords.size(); x++) {
        vector<Keyword> keywords;
        keyword_list.KeywordsByAlias(keywords, filter.keywords[x]);
        for (unsigned ki=0; ki<keywords.size(); ki++) {
            items.push_back(TextItem(wxFormat(_("{{%s}} could be an alias of {{%s}}"), filter.keywords[x], keywords[ki].name)));
        }
    }

    // load notes from database
    // it's possible to get event loop while we're inside DB routine... why?
    dbg("Loading notes\n");
    vector<Note> notes;
    if (!db->SelectByFilter(&notes, filter))
        Close(false);
    for (unsigned x=0; x<notes.size(); x++)
        items.push_back(notes[x]);

    // update after proper note is selected
    UpdateDisplay();

    // if previously selected note is still here, select it,
    // otherwise select first note (if any)
    dbg("Trying to restore selection\n");
    if (old_selection_id != -1  &&  ContainsNoteID(old_selection_id))
        SetSelection(FindNoteIndexByID(old_selection_id));
    else if (items.size() >= 1)
        SetSelection(0);
}

void NotesPanel::GetFilter(NoteFilter& filter)
{
    dbgenter("NotesPanel::GetFilter()\n");
    filter = this->filter;
}

void NotesPanel::NotifyNewNote(const Note& n)
{
    dbgenter("NotesPanel::NotifyNewNote(%s)\n", n.ToString());

    // check if note matches current filter
    if (!filter.Matches(n))
        return;

    InsertNote(n);
}


void NotesPanel::NotifyEditedNote(const Note& n)
{
    dbgenter("NotesPanel::NotifyEditedNote(%s)\n", n.ToString());

    int index = FindNoteIndexByID(n.id);
    if (index == -1) {                      // note wasn't here?
        if (filter.Matches(n)) {            //  edited to match filter?
            InsertNote(n);
        }
    } else {                                // edited one of notes displayed here?
        if (filter.Matches(n)) {            //  still matches current filter?
            int old_selection_index=selection_index;
            RemoveItemAt(index);
            InsertNote(n);
            if (index == old_selection_index)
                SetSelection(FindNoteIndexByID(n.id));
        } else {                            //  no longer matches filter?
            RemoveItemAt(index);
        }
    }
}

void NotesPanel::NotifyDeletedNote(const Note& n)
{
    dbgenter("NotesPanel::NotifyDeletedNote(%s)\n", n.ToString());

    RemoveNote(n);
}

void NotesPanel::InsertKeyword(const Keyword& k)
{
    // TODO: keep keywords sorted
    InsertItemAt(TextItem(k), 0);
}

void NotesPanel::NotifyDeletedKeyword(const Keyword& k)
{
    dbgenter("NotesPanel::NotifyDeletedKeyword(%s)\n", k.ToString());
    RemoveKeyword(k);
}

void NotesPanel::NotifyNewKeyword(const Keyword& k)
{
    dbgenter("NotesPanel::NotifyNewKeyword(%s)\n", k.ToString());
    if (IsSingleKeywordFilter(filter) && wxString_ContainsI(k.name, filter.keywords[0]))
        InsertKeyword(k);
}

void NotesPanel::NotifyEditedKeyword(const Keyword& k)
{
    dbgenter("NotesPanel::NotifyEditedKeyword(%s)\n", k.ToString());

    if (!IsSingleKeywordFilter(filter))
        return;

    int index = FindKeywordIndexByID(k.id);
    if (index == -1) {                      // keyword wasn't here?
        if (wxString_ContainsI(k.name, filter.keywords[0])) {  //  edited to match filter?
            InsertKeyword(k);
        }
    } else {                                // edited one of keywords displayed here?
        if (wxString_ContainsI(k.name, filter.keywords[0])) { //  still matches current filter?
            int old_selection_index=selection_index;
            RemoveItemAt(index);
            InsertKeyword(k);
            if (index == old_selection_index)
                SetSelection(FindKeywordIndexByID(k.id));
        } else {                            //  no longer matches filter?
            RemoveItemAt(index);
        }
    }
}
